package com.zhangyx.core;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.Args;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.*;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class HttpClientUtil implements Serializable{

    private static Logger log = Logger.getLogger(HttpClientUtil.class);
    private static PoolingHttpClientConnectionManager poolManager = new PoolingHttpClientConnectionManager();


    private String url = null;
    private String html = null;
    private Boolean useGET = true;
    private int responseCode = 200;
    private HttpHost proxy = null;
    private CookieStore cookieStore = null;
    private HttpClientContext context = null;


    private HttpGet httpGet = null;
    private HttpPost httpPost = null;
    private CloseableHttpClient httpclient = null;
    private CloseableHttpResponse response = null;


    //超时时间
    public static final int socketTimeOut = 10000;
    // 是整个池子的大小
    private static final int maxTotal = 30;
    // 根据连接到的主机对MaxTotal的一个细分
    private static final int maxPerRoute = 4;

    /**
     * 重写验证方法，取消检测ssl
     */
    static class TrustMana implements TrustManager,X509TrustManager{

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(X509Certificate[] certs) {
            return true;
        }

        public void checkServerTrusted(X509Certificate[] certs, String authType)
                throws CertificateException {
            return;
        }

        public void checkClientTrusted(X509Certificate[] certs, String authType)
                throws CertificateException {
            return;
        }
    }

    /**
     * 创建连接池
     * @param useDNS 是否使用dns缓存
     */
    public static void initPool(Boolean useDNS) {
        try {
            SSLContext sslcontext = SSLContext.getInstance("SSL");
            sslcontext.init(null, new TrustManager[] { new TrustMana() }, null);
            Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory> create()
                    .register("http", new PlainConnectionSocketFactory())
                    .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
            if (useDNS) {
                poolManager = new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver());
            }else {
                poolManager = new PoolingHttpClientConnectionManager(reg);
            }
            poolManager.setMaxTotal(maxTotal);
            poolManager.setDefaultMaxPerRoute(maxPerRoute);
            poolManager.setDefaultConnectionConfig(ConnectionConfig.custom().setCharset(Charset.defaultCharset()).build());
            poolManager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(socketTimeOut).build());
        } catch (Exception e) {

        }

    }

    /**
     * 创建请求实例
     *
     * @param useGET
     * @param url
     * @return
     */
    public static HttpClientUtil build(Boolean useGET, String url) {

        return new HttpClientUtil(useGET, url);
    }

    private HttpClientUtil(Boolean useGET, String url){
        this.useGET = useGET;
        this.url = url;
        if (useGET) {
            httpGet = new HttpGet(url);
        }else {
            httpPost = new HttpPost(url);
        }
        context = HttpClientContext.create();
        cookieStore = new BasicCookieStore();
    }



    /**
     *  执行请求
     * @return
     */
    public HttpClientUtil start() {
        try {
            httpclient = HttpClients.custom().setConnectionManager(poolManager).setProxy(proxy).build();
            context.setCookieStore(cookieStore);
            if (useGET) {
                response = httpclient.execute(httpGet,context);
            }else {
                response = httpclient.execute(httpPost,context);
            }
            HttpEntity entity = response.getEntity();
            html = getContent(entity.getContent());
            responseCode = response.getStatusLine().getStatusCode();
            EntityUtils.consume(entity);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("爬取网页失败: " + url+"\r\n"+e.getMessage());
        }
        return this;
    }

    /**
     * 添加post请求的入参
     * @param paras
     * @return
     */
    public HttpClientUtil addEntities(Map<String, String> paras) {
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        Set<String> keys = paras.keySet();
        for (String key : keys) {
            nvps.add(new BasicNameValuePair(key, paras.get(key)));
        }
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nvps));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;
    }

    /**
     * post请求中使用json格式的参数
     * @param json
     * @return
     */
    public HttpClientUtil addJsonEntity(JSONObject json) {
        StringEntity entity = new StringEntity(json.toString(),"utf-8");//解决中文乱码问题
        try {
            httpPost.setEntity(entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;

    }

    /**
     * post请求中使用json格式的参数
     * @param entity
     * @return
     */
    public HttpClientUtil addEntity(Object entity) {
        try {
            if (Map.class.isInstance(entity)) {
                addEntities((Map<String, String>) entity);
            } else if (JSONObject.class.isInstance(entity)) {
                addJsonEntity((JSONObject) entity);
            }else {
                log.error("传递的参数不符合格式");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return this;

    }

    /**
     * 添加cookie列表
     * @param cookies
     * @param domain
     * @param path
     * @return
     */
    public HttpClientUtil addCookies(Map<String, String> cookies, String  domain, String path) {
        Set<String> keys = cookies.keySet();
        for (String key : keys) {
            BasicClientCookie cookie = new BasicClientCookie(key, cookies.get(key));
            cookie.setDomain(domain);
            cookie.setPath(path);
            cookieStore.addCookie(cookie);
        }
        return this;
    }

    /**
     * 添加cookie
     * @param name
     * @param value
     * @param domain
     * @param path
     * @return
     */
    public HttpClientUtil addCookie(String name, String value, String  domain, String path) {
        BasicClientCookie cookie = new BasicClientCookie(name, value);
        cookie.setDomain(domain);
        cookie.setPath(path);
        cookieStore.addCookie(cookie);
        return this;
    }

    /**
     * 添加请求头部
     * @param name
     * @param value
     * @return
     */
    public HttpClientUtil addHeader(String name, String value) {
        if (useGET) {
            httpGet.addHeader(name, value);
        } else {
            httpPost.addHeader(name, value);
        }
        return this;
    }

    /**
     * 设置请求头部
     * @param headers
     * @return
     */
    public HttpClientUtil addHeaders(Map<String,String> headers) {
        Set<String> keys = headers.keySet();
        for (String name : headers.keySet()) {
            String value = headers.get(name);
            this.addHeader(name, value);
        }
        return this;
    }

    /**
     * 从response的输入流中取出网页内容
     * @param ins
     * @return
     */
    private static String getContent(InputStream ins) {
        StringBuffer sbf = new StringBuffer("");
        BufferedReader br = new BufferedReader(new InputStreamReader(ins));
        String temp = null;
        try {
            while ((temp = br.readLine()) != null) {
                sbf.append(temp + "\r\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                ins.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sbf.toString();
    }

    /**
     * 设置代理
     * @param hostName
     * @param port
     * @param schemme  http https 暂时不支持socks
     * @return
     */
    public HttpClientUtil setProxy(String hostName, int port, String schemme) {
        proxy = new HttpHost(hostName, port, schemme);
        return this;
    }

    public int getResponseCode() {
        return responseCode;
    }

    public String getHtml() {
        return html;
    }

    public CookieStore getCookieStore() {
        return cookieStore;
    }

    public void setCookieStore(CookieStore cookieStore) {
        this.cookieStore = cookieStore;
    }

    public String getUrl() {
        return url;
    }

    public void setHtml(String html) {
        this.html = html;
    }

    /**
     * dns 缓存
     */
    static class FakeDnsResolver implements DnsResolver {

        private static final Map<String, InetAddress[]> dnsMap = new ConcurrentHashMap<String, InetAddress[]>();

        private static final long DNS_DEADLINE_MINUTE = 120 * 60000;

        private static Long lastClearTime = System.currentTimeMillis();

        public void add(final String host, final InetAddress... ips) {
            Args.notNull(host, "Host name");
            Args.notNull(ips, "Array of IP addresses");
            dnsMap.put(host, ips);
        }

        public InetAddress[] resolve(String host) throws UnknownHostException {
            synchronized (lastClearTime) {
                if(System.currentTimeMillis() - lastClearTime > DNS_DEADLINE_MINUTE){
                    dnsMap.clear();
                    lastClearTime = System.currentTimeMillis();
                }
            }

            InetAddress[] resolvedAddresses = dnsMap.get(host);
            if(resolvedAddresses == null){
                resolvedAddresses = InetAddress.getAllByName(host);
                if (resolvedAddresses!= null){
                    dnsMap.put(host, resolvedAddresses);
                }else{
                    log.error("DNS缓存--域名解析失败: " + host);
                }
            }
            return resolvedAddresses;
        }
    }
}
